package com.wordpress.standal.multimethod.api.class1

import scala.collection.mutable.ListBuffer

class MultiMethod [C1 <: AnyRef, C2, R] {
  
  private class Entry(val cls: Class[_ <: C1], val func: Function2[C1, C2, R])
  
  private val entries: ListBuffer[Entry] = new ListBuffer()
    
  def add[S <: C1](cls: Class[S], func: Function2[S, C2, R]) {
    entries += new Entry(cls, new Function2[C1, C2, R]() {
      def apply(c1 : C1, c2: C2) = func.apply(c1.asInstanceOf[S], c2)
    })
  }
  
  def apply(c1: C1, c2: C2) : R = {
    var current: Entry = null;
    for (e: Entry <- entries) {
      if (e.cls.isAssignableFrom(c1.getClass)) {
        if (current == null) {
          current = e
        } else {
          if (current.cls.isAssignableFrom(e.cls)) {
            current = e
          }
        }
      }
    }
    current.func.apply(c1, c2)
  }

}

object MultiMethod {
  
  class MultiMethod0 [C <: AnyRef, R] {
    val rep = new MultiMethod[C, Unit, R]
    def apply(c : C) = {
      rep.apply(c, null)
    }
    def add[S <: C](cls: Class[S], func: Function1[S, R]) {
      rep.add(cls, new Function2[S, Unit, R]() {
        def apply(x : S, u : Unit) = func.apply(x)
      })
    }
  }

  class MultiMethod2 [C1 <: AnyRef, C2, C3, R] {
    val rep = new MultiMethod[C1, (C2, C3), R]
    def apply(c1 : C1, c2: C2, c3: C3) = {
      rep.apply(c1, (c2, c3))
    }
    def add[S <: C1](cls: Class[S], func: Function3[S, C2, C3, R]) {
      rep.add(cls, new Function2[S, (C2, C3), R]() {
        def apply(x : S, t2: (C2, C3)) = func.apply(x, t2._1, t2._2)
      })
    }
  }

  def apply[C <: AnyRef, R]() = {
    new MultiMethod0[C, R]()
  }
  
  def apply[C1 <: AnyRef, C2, R]() = {
    new MultiMethod[C1, C2, R]()
  }

  def apply[C1 <: AnyRef, C2, C3, R]() = {
    new MultiMethod2[C1, C2, C3, R]()
  }

}
